Skip to content

feat(richtext): type richtext field value from OpenAPI spec - #684

Open
dipankarmaikap wants to merge 10 commits into
mainfrom
fix/DX-487-richtext-openapi-types
Open

feat(richtext): type richtext field value from OpenAPI spec#684
dipankarmaikap wants to merge 10 commits into
mainfrom
fix/DX-487-richtext-openapi-types

Conversation

@dipankarmaikap

@dipankarmaikap dipankarmaikap commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Types the richtext field value in OpenAPI format and wires @storyblok/richtext and storyblok (CLI) into the codegen pipeline so the node/mark types are generated from the spec rather than maintained by hand.

Changes

tools/openapi-codegen

  • richtext-field-value.yaml — expanded from a loose type: object into a fully discriminated schema with $defs for all 17 node types and 12 mark types, mirroring richtext-attrs.ts
  • overlay.openapi.yaml — added RichTextNode and RichTextMark as top-level schema refs so they are emitted as named types
  • aliases.ts — registered RichtextDoc, RichTextNode, RichTextMark
  • index.ts — fixed copyWrapperTemplates to skip writing the empty types/_sources.ts when no wrapper templates are requested

packages/richtext

  • scripts/generate.ts — new OpenAPI codegen entry point (mirrors live-preview), generates RichtextDoc, RichTextNode, RichTextMark into src/generated/
  • src/generated/overlay/types.gen.ts — committed generated output
  • package.json — added generate:openapi script, @storyblok/openapi-codegen devDep, and nx target
  • src/static/generate/richtext-type.ts — stop generating SbRichTextNode/SbRichTextMark as hand-rolled unions; delegate to OpenAPI-generated types with _key, context, and common optional fields intersected in
  • src/static/types.tsSbRichTextDoc is now RichtextDoc; SbRichTextInput explicitly includes SbRichTextDoc
  • test-utils — updated to use SbRichTextMark[] directly and NonNullable<SbRichTextInput> for fixture types

packages/cli (storyblok)

  • scripts/generate.ts — new OpenAPI codegen entry point, generates RichtextDoc, RichTextNode, RichTextMark into src/generated/
  • src/generated/overlay/types.gen.ts — committed generated output
  • package.json — added generate:openapi script, @storyblok/openapi-codegen devDep, and nx target
  • src/types/storyblok.ts — replaced the hand-written StoryblokRichtext interface (loose type: string with any attrs) with a backward-compatible alias to RichtextDoc; also exports RichTextNode and RichTextMark

Testing

  • pnpm nx run @storyblok/richtext:test:types
  • pnpm nx run @storyblok/richtext:test ✅ (174 tests)

Fixes DX-487

Expands richtext-field-value.yaml from a loose `type: object` into a
fully discriminated schema with $defs for every node and mark type,
mirroring the TypeScript types in richtext-attrs.ts.

Wires @storyblok/richtext into the openapi-codegen pipeline:
- scripts/generate.ts generates RichtextDoc, RichTextNode, RichTextMark
  from the overlay spec into src/generated/
- RichtextDoc / RichTextNode / RichTextMark added to overlay.openapi.yaml
  as top-level schemas and to aliases.ts so they can be included by name
- generate:openapi nx target added; existing generate target (Tiptap-based)
  is unchanged

Reduces manual type maintenance in the richtext package:
- SbRichTextNode and SbRichTextMark are no longer hand-rolled unions in
  types.generated.ts; they delegate to the OpenAPI-generated RichTextNode
  and RichTextMark with _key, context, and common optional fields
  intersected in for renderer use
- SbRichTextDoc is now RichtextDoc (the OpenAPI root type) instead of
  SbRichTextNode & { type: 'doc' } which became never after the change
- SbRichTextInput extended to include SbRichTextDoc explicitly

Fixes copyWrapperTemplates in openapi-codegen to skip writing the empty
types/_sources.ts when no wrapper templates are requested.

Fixes DX-487
…s.ts

Make attrs required on all nodes/marks that define an attrs object, and
add inner required arrays matching the non-optional fields declared in
richtext-attrs.ts (LinkAttrs, ParagraphAttrs, HeadingAttrs, etc.).

Also fix two structural issues surfaced by the stricter types:
- NoAttrs: Record<string, never> → Record<string, unknown> to be
  compatible with the Record<string, unknown> that SbRichTextMark
  adds via intersection for attr-less marks (bold, italic, etc.)
- BlokNode body items: remove additionalProperties: true whose
  [key: string]: unknown index signature was incompatible with
  SbBlokKeyDataTypes in SbBlokData
@pkg-pr-new

pkg-pr-new Bot commented Jul 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

@storyblok/angular

npm i https://pkg.pr.new/@storyblok/angular@684

@storyblok/astro

npm i https://pkg.pr.new/@storyblok/astro@684

@storyblok/api-client

npm i https://pkg.pr.new/@storyblok/api-client@684

storyblok

npm i https://pkg.pr.new/storyblok@684

@storyblok/eslint-config

npm i https://pkg.pr.new/@storyblok/eslint-config@684

@storyblok/js

npm i https://pkg.pr.new/@storyblok/js@684

storyblok-js-client

npm i https://pkg.pr.new/storyblok-js-client@684

@storyblok/live-preview

npm i https://pkg.pr.new/@storyblok/live-preview@684

@storyblok/management-api-client

npm i https://pkg.pr.new/@storyblok/management-api-client@684

@storyblok/migrations

npm i https://pkg.pr.new/@storyblok/migrations@684

@storyblok/nuxt

npm i https://pkg.pr.new/@storyblok/nuxt@684

@storyblok/react

npm i https://pkg.pr.new/@storyblok/react@684

@storyblok/region-helper

npm i https://pkg.pr.new/@storyblok/region-helper@684

@storyblok/richtext

npm i https://pkg.pr.new/@storyblok/richtext@684

@storyblok/svelte

npm i https://pkg.pr.new/@storyblok/svelte@684

@storyblok/vue

npm i https://pkg.pr.new/@storyblok/vue@684

commit: 68b2d5d

@dipankarmaikap
dipankarmaikap marked this pull request as draft July 8, 2026 13:28
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

⚠️ Cross-package changes detected

This PR is titled as a feat(richtext) commit but modifies files across 5 packages:

  • packages/angular/
  • packages/cli/
  • packages/js/
  • packages/richtext/
  • packages/vue/

When this PR is squash-merged, all changes become a single feat(richtext) commit.
Nx release uses file changes (not the commit scope) to determine which packages to bump,
so every package listed above will get a version bump on the next release.

If the changes to other packages are cosmetic (README fixes, dependency bumps, config cleanup),
please split them into a separate PR with a chore: title. chore commits don't trigger version bumps.

pnpm v10 no longer reads the "pnpm" field in package.json.
Move overrides to pnpm-workspace.yaml as required by the new config.
Add scripts/generate.ts and commit src/generated/overlay/types.gen.ts
mirroring the same setup as @storyblok/richtext. Includes the
generate:openapi nx target and @storyblok/openapi-codegen devDep.
…type

Drop the loose hand-written interface and re-export RichtextDoc (and
RichTextNode, RichTextMark) from src/generated/overlay/types.gen.ts.
StoryblokRichtext is kept as a backward-compatible alias for RichtextDoc.

@alexjoverm alexjoverm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dipankarmaikap added a few minor notes, but will leave to @maoberlehner the final review.

Important note: if we squash & merge, this PR will trigger a new version on Angular, CLI, JS, Richtext and Vue SDKs. If that's intended, fine - otherwise, we need to rebase the commits and not squash when merging

@@ -0,0 +1,29 @@
#!/usr/bin/env -S node --experimental-strip-types --no-warnings=ExperimentalWarning

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(non-blocking): are the flags in this line needed? Shouldn't it be #!/usr/bin/env tsx like in the clients/generate.ts?

Comment thread .prettierrc
@@ -0,0 +1,7 @@
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dipankarmaikap I would keep this out of the scope of this PR - we have a ticket to revisit formatting using OXC

export type { RichtextDoc, RichTextMark, RichTextNode } from '../generated/overlay/types.gen';

// Backward-compatible alias
export type { RichtextDoc as StoryblokRichtext } from '../generated/overlay/types.gen';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it's exported as an alias, the type is stricter than before - it's fine if we want to publish it like this, as it's a type fix, but let's keep in mind some users might take it as a breaking change as technically it might be.

export type SbRichTextDoc = RichtextDoc;
export type SbRichTextTextNode = SbRichTextNode & { type: 'text' };
export type SbRichTextInput = SbRichTextNode | SbRichTextNode[] | null | undefined;
export type SbRichTextInput = SbRichTextDoc | SbRichTextNode | SbRichTextNode[] | null | undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type is different than before - it's fine if we want to publish it like this, as it's a type fix, but let's keep in mind some users might take it as a breaking change as technically it might be.

Comment thread package.json
"resolutions": {
"cypress": "^14.3.3"
},
"pnpm": {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(non-blocking): same here - it's not related to the PR. No issue keeping it as it's low risk, but better to tackle it in its own PR moving forward. Will give better visibility, for example, in cases where a regression is introduced

Comment thread pnpm-workspace.yaml
@@ -6,3 +6,5 @@ packages:
onlyBuiltDependencies:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$ref: ./shared/stories/field-types/plugin-field-value.yaml
RichtextFieldValue:
$ref: ./shared/stories/field-types/richtext-field-value.yaml
RichTextNode:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming of the Richtext / Rich Text field already is a mess, but at least we should not make it worse. The overlay currently uses Richtext. Let's keep it that way. And/or talk to docs and product about what the right name is (in the Storyblok UI, the field is named Richtext).

id:
type: [string, 'null']
description: Blok instance ID
body:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use:

body:
  oneOf:
    - type: array
      items:
        $ref: ../block-content.yaml   # from field-types/ dir
    - type: 'null'

properties:
id:
type: [string, 'null']
description: Blok instance ID

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to docs team, we should use Block not Blok.

dipankarmaikap added a commit that referenced this pull request Jul 31, 2026
## Summary

Extracts the `tools/openapi-codegen` changes from #684 into a standalone
PR so the spec work can be reviewed and merged independently.

## Changes

### `tools/openapi-codegen`

**Spec (`specs/`)**
- `richtext-field-value.yaml` — expanded from a loose `type: object`
into a fully discriminated schema with `$defs` for all 17 node types and
12 mark types
- `BlockNode.attrs.body` items reference `../block-content.yaml`
(canonical definition) instead of an inline duplicate
- `overlay.openapi.yaml` — added `RichTextNode` and `RichTextMark` as
top-level schema refs so they are emitted as named types; renamed
`RichtextFieldValue` → `RichTextFieldValue`
- `mapi/components/field-types/richtext-field.yaml` — description
updated to follow naming conventions

**Source (`src/`)**
- `aliases.ts` — registered `RichTextDoc`, `RichTextNode`,
`RichTextMark`; updated `RichtextFieldValue` → `RichTextFieldValue`
- `index.ts` — fixed `copyWrapperTemplates` to skip writing the empty
`types/_sources.ts` when no wrapper templates are requested
- `known-types.ts`, `templates.ts` — updated `RichtextFieldValue` →
`RichTextFieldValue`; registered `RichtextFieldValue` in `provides` for
backward compat

**Templates (`templates/`)**
- `field.ts` — updated import/export/map entry for `RichTextFieldValue`;
added deprecated `RichtextFieldValue` alias

## Backward compatibility

`RichtextFieldValue` has been renamed to `RichTextFieldValue`. To avoid
breaking consumer packages all at once, the generated `field.ts` now
also exports:

```ts
/** @deprecated Use RichTextFieldValue instead. Will be removed in a future major version. */
export type RichtextFieldValue = RichTextFieldValue;
```

This means packages that currently import `RichtextFieldValue`
(`mapi-client`, `schema`, `capi-client`, `migrations`, etc.) will
continue to compile after regeneration — TypeScript/editors will surface
the `@deprecated` warning so each package can migrate at its own pace.

## Naming conventions applied

| Context | Convention | Example |
|---|---|---|
| Type names | `RichText` (PascalCase) | `RichTextFieldValue`,
`RichTextDoc` |
| Sentence start | `Rich text` | `Rich text field type …` |
| Mid-sentence | `rich text` | `… rich text document …` |
| Wire values | unchanged | `enum: [richtext]`, `toggle-richtext` |
| Node type names | `Block` not `Blok` | `BlockNode` |

Fixes DX-487
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants